This section supplies a sample program that shows how to use a sequence grabber component to preview and record captured data. The program is divided into groups of functions that do the following tasks:
Listing 1 provides a sample function that creates and initializes a default sequence grabber component for a specified window (using the OpenDefaultComponent and SGInitialize functions, respectively). It then sets the graphics world of the sequence grabber component to the specified window with the SGSetGWorld function. Note that the CloseComponent function is called for housekeeping purposes in case the sequence grabber component fails. For more on OpenDefaultComponent and CloseComponent , see the chapter "Component Manager" in Inside Macintosh: More Macintosh Toolbox . For details on SGInitialize and SGSetGWorld , see SGInitialize and SGSetGWorld , respectively.
Listing 1 Initializing a sequence grabber component
SeqGrabComponent MakeSequenceGrabber (WindowPtr aWindow)
{
SeqGrabComponent anSG;
OSErr err = noErr;
/* open up the default sequence grabber */
anSG = OpenDefaultComponent (SeqGrabComponentType, 0);
if (anSG) {
/* initialize the default sequence grabber component */
err = SGInitialize (anSG);
if (!err) {
/* set the sequence grabber's graphics world to the
specified window */
err = SGSetGWorld (anSG, (CGrafPtr) aWindow, nil);
}
}
if (err && anSG) {
/* clean up on failure */
CloseComponent (anSG);
anSG = nil;
}
return anSG;
}
Listing 2 supplies a sample function that attempts to create a video channel and a sound channel for the sequence grabber component that was created in Listing 1 . The boundaries of the video channel are set to the specifications of the bounds parameter. The channel's usage is always set to allow previewing. If the value of the willRecord parameter is true , then the usage of the channel is set to allow recording also.
The SGNewChannel function (described on SGNewChannel ) uses the VideoMediaType constant to create a video channel and the SoundMediaType constant to create a sound channel. The SGSetChannelBounds function (described on SGSetChannelBounds ) specifies the boundaries of the video channel. The SGSetChannelUsage function (described on SGSetChannelUsage ) specifies whether the video and the sound channels are used for preview or record operations. The SGDisposeChannel function (described on SGDisposeChannel ) cleans up upon failure for each of the channels.
Listing 2 Creating a sound channel and a video channel
void MakeGrabChannels (SeqGrabComponent anSG,
SGChannel *videoChannel,
SGChannel *soundChannel,
const Rect *bounds, Boolean willRecord)
{
OSErr err;
long usage;
/* figure out the usage */
usage = seqGrabPreview; /* always previewing */
if (willRecord)
usage |= seqGrabRecord; /* sometimes recording */
/* create a video channel */
err = SGNewChannel (anSG, VideoMediaType, videoChannel);
if (!err) {
/* set boundaries for new video channel */
err = SGSetChannelBounds (*videoChannel, bounds);
/* set usage for new video channel */
if (!err)
err = SGSetChannelUsage (*videoChannel,
usage | seqGrabPlayDuringRecord);
if (err) {
/* clean up on failure */
SGDisposeChannel (anSG, *videoChannel);
*videoChannel = nil;
}
}
/* create a sound channel */
err = SGNewChannel (anSG, SoundMediaType, soundChannel);
if (!err) {
/* set usage of new sound channel */
err = SGSetChannelUsage (*soundChannel, usage);
if (err) {
/* clean up on failure */
SGDisposeChannel(anSG, *soundChannel);
*soundChannel = nil;
}
}
}
Listing 3 shows how to use the sequence grabber component to preview sound and video sequences in a window. Clicking the content area of the window causes the sequence grabber to pause until the mouse button is released.
The Image Compression Manager's GetBestDeviceRect function helps you determine the best monitor for the window. The SGStartPreview function (described on SGStartPreview ) begins the preview of the sound and video sequences. The SGIdle function (described on SGIdle ) grants the sequence grabber component the time it needs to preview data. The SGUpdate function (described on SGUpdate ) informs the sequence grabber of the update event. The Window Manager's BeginUpdate and EndUpdate functions respond to the event. The SGPause function (described on SGPause ) instructs the sequence grabber to suspend and resume its preview operation. In this example, it is used to suspend the preview operation while the mouse button is held down. Finally, the SGStop function (described on SGStop ) halts the action of the sequence grabber component. The Component Manager's CloseComponent function closes the component connection. The Window Manager's DisposeWindow function disposes of the window.
Listing 3 Previewing sound and video sequences in a window
void CheckError(OSErr error, Str255 displayString)
{
if (error == noErr) return;
if (displayString[0] > 0)
DebugStr(displayString);
ExitToShell();
}
Boolean IsQuickTimeInstalled (void)
{
short error;
long result;
error = Gestalt (gestaltQuickTime, &result);
return (error == noErr);
}
void initialize (void)
{
OSErr err;
InitGraf (&qd.thePort);
InitFonts ();
InitWindows ();
InitMenus ();
TEInit ();
InitDialogs (nil);
MaxApplZone();
if (!IsQuickTimeInstalled())
CheckError(-1,"\pPlease install QuickTime and try again.");
err = EnterMovies ();
CheckError(err,"\pUnable to initialize Movie Toolbox.");
}
WindowPtr makeWindow(void)
{
WindowPtr aWindow;
Rect windowRect = {0, 0, 120, 160};
Rect bestRect;
/* figure out the best monitor for the window */
GetBestDeviceRect (nil, &bestRect);
/* put the window in the top left corner of that monitor */
OffsetRect(&windowRect, bestRect.left + 10, bestRect.top + 50);
/* create the window */
aWindow = NewCWindow (nil, &windowRect, "\pGrabber",
true, noGrowDocProc, (WindowPtr)-1,
true, 0);
/* and set the port to the new window */
SetPort(aWindow);
return aWindow;
}
main (void)
{
WindowPtr theWindow;
SeqGrabComponent theSG;
SGChannel videoChannel, soundChannel;
Boolean done = false;
OSErr err;
initialize();
theWindow = makeWindow();
theSG = makeSequenceGrabber(theWindow);
if (!theSG) return;
makeGrabChannels(theSG, &videoChannel, &soundChannel,
&theWindow->portRect, false);
if ((videoChannel == nil) && (soundChannel == nil))
CheckError(-1,"\pNo sound or video available.");
err = SGStartPreview(theSG);
CheckError(err, "\pCan't start preview");
while (!done) {
AlignmentProcRecord alignProc;
short part;
WindowPtr whichWindow;
EventRecord theEvent;
GetNextEvent(everyEvent, &theEvent);
switch (theEvent.what) {
case nullEvent: /* give the sequence grabber time */
err = SGIdle (theSG);
if (err) done = true;
break;
case updateEvt:if (theEvent.message == (long)theWindow) {
/* inform the sequence grabber of the
update */
SGUpdate(theSG,((WindowPeek)
theWindow)->updateRgn);
/* and swallow the update event */
BeginUpdate(theWindow);
EndUpdate(theWindow);
}
break;
case mouseDown:part = FindWindow (theEvent.where,
&whichWindow);
if (whichWindow != theWindow) break;
switch (part) {
case inContent:
/* pause until mouse button is
released */
SGPause (theSG, true);
while (StillDown())
;
SGPause(theSG, false);
break;
case inGoAway:
done = TrackGoAway (theWindow,
theEvent.where);
break;
case inDrag:
/* pause when dragging window so video
doesn't draw in the wrong place */
SGPause (theSG, true);
SGGetAlignmentProc (theSG, &alignProc);
DragAlignedWindow (theWindow,
theEvent.where,
&screenBits.bounds,
nil, &alignProc);
SGPause (theSG, false);
break;
}
break;
}
}
/* clean up */
SGStop (theSG);
CloseComponent (theSG);
DisposeWindow (theWindow);
}
Listing 4 uses the sequence grabber component to capture ten seconds of sound and video data. It prompts the user for the name of the file to create. The SGSettingsDialog function (described on SGSettingsDialog ) is issued to invoke the default sound and video capture settings dialog boxes. These default dialog boxes allow the user to configure the settings for the capture operations. The SGSetMaximumRecordTime function (described on SGSetMaximumRecordTime ) indicates how long the capture operations will last. The SGStartRecord function (described on SGStartRecord ) specifies the time at which the capture operations will begin. The SGIdle function (described on SGIdle ) grants the time needed to confirm the capture operations. Finally, the SGStop function (described on SGStop ) and the Window Manager's DisposeWindow routine are called in order to complete the capture of the sequences.
Listing 4 Capturing sound and video
main (void)
{
WindowPtr theWindow;
CGrafPort tempPort;
SeqGrabComponent theSG;
SGChannel videoChannel, soundChannel;
OSErr err;
initialize();
theWindow = makeWindow();
theSG = makeSequenceGrabber(theWindow);
if (!theSG) return;
err = setGrabFile(theSG);
CheckError(err, "\pNo output file");
makeGrabChannels (theSG, &videoChannel, &soundChannel,
&theWindow->portRect, true);
if ((videoChannel == nil) && (soundChannel == nil))
CheckError(-1,"\pNo sound or video available.");
if (videoChannel)
SGSettingsDialog (theSG, videoChannel, 0, nil,
DoTheRightThing, nil, 0);
if (soundChannel)
SGSettingsDialog(theSG, soundChannel, 0, nil,
DoTheRightThing, nil, 0);
err = SGSetMaximumRecordTime(theSG, 10 * 60);
CheckError(err, "\pCan't set max record time");
err = SGStartRecord (theSG);
CheckError(err, "\pCan't start record");
while (!err)
err = SGIdle (theSG);
if (err == grabTimeComplete)
err = noErr;
CheckError(err, "\pError while recording");
err = SGStop(theSG);
CheckError(err, "\pError creating movie");
CloseComponent(theSG);
DisposeWindow(theWindow);
}
Listing 5 shows how to set up the video bottleneck functions of the sequence grabber video channel component. For more information on the video bottleneck functions, see "Utility Functions for Video Channel Callback Functions," . Inside the main event loop in Listing 4 , you should add the following lines after you call the SGSetMaximumRecordTime function (described on SGSetMaximumRecordTime ).
Listing 5Setting up the video bottleneck functions
if (videoChannel) {
err = setupVideoBottlenecks (videoChannel, theWindow,
&tempPort);
CheckError(err, "\pCouldn't set video bottlenecks");
}
Listing 6 shows how to use the video bottleneck functions of the sequence grabber video channel component to draw the letters "QT" over each video frame as it is captured.
Listing 6 Drawing information over video frames during capture
pascal ComponentResult myGrabFrameComplete (SGChannel c,
short bufferNum,
Boolean *done,
long refCon)
{
ComponentResult err;
/* call the default grab-complete function */
err = SGGrabFrameComplete (c, bufferNum, done);
if (*done) {
/* frame is done */
CGrafPtr savePort;
GDHandle saveGD;
PixMapHandle bufferPM, savePM;
Rect bufferRect;
CGrafPtr tempPort = (CGrafPtr)refCon;
/* set to our temporary port */
GetGWorld (&savePort, &saveGD);
SetGWorld (tempPort, nil);
/* find out about this buffer */
err = SGGetBufferInfo (c, bufferNum, &bufferPM, &bufferRect,
nil, nil);
if (!err) {
/* set up to draw into this buffer */
savePM = tempPort->portPixMap;
SetPortPix(bufferPM);
/* draw some text into the buffer */
TextMode (srcXor);
MoveTo (bufferRect.right - 20, bufferRect.bottom - 14);
DrawString ("\pQT");
TextMode(srcOr);
/* restore temporary port */
SetPortPix (savePM);
}
SetGWorld (savePort, saveGD);
}
return err;
}
OSErr setupVideoBottlenecks (SGChannel videoChannel, WindowPtr w,
CGrafPtr tempPort)
{
OSErr err;
err = SGSetChannelRefCon (videoChannel, (long)tempPort);
if (!err) {
VideoBottles vb;
/* get the current bottlenecks */
vb.procCount = 9;
err = SGGetVideoBottlenecks (videoChannel, &vb);
if (!err) {
/* add our GrabFrameComplete function */
vb.grabCompleteProc = myGrabFrameComplete;
err = SGSetVideoBottlenecks (videoChannel, &vb);
/* set up the temporary port */
OpenCPort (tempPort); /* create a temporary port
for drawing */
SetRectRgn (tempPort->visRgn, -32000, -32000, 32000,
32000); /* with a wide open visible
and clip region . . . */
CopyRgn (tempPort->visRgn, tempPort->clipRgn);
/* so that you can use it in
any video buffer */
PortChanged ((GrafPtr)tempPort);
/* tell QuickDraw about the
changes */
}
}
return err;
}